#include "ex06fa.cpp"
#include <stdint.h>
#include <list>

class BazQueueThread : public QueueThread
{
public:
	enum BazMessageTypes {
		MSG_REQUEST
	};
	// the request that will be sent to baz
	class BazRequestMessage : public QueueMessage
	{
	public:
		int arg_; // the same argument as before
		QueueThread *requestor_; // where to send the reply

		BazRequestMessage(int rettype, void *context, 
				QueueThread *requestor, int arg) :
			QueueMessage(MSG_REQUEST, rettype, context),
			arg_(arg), requestor_(requestor)
		{ }
	};
	// results returned from baz
	class BazReplyMessage : public QueueMessage
	{
	public:
		int result_;

		BazReplyMessage(int type, void *context, 
				int result) :
			QueueMessage(type, -1, context),
			result_(result)
		{ }
	};

	BazQueueThread() :
		counter_(0)
	{ }

protected:
	void dispatch(QueueMessage *m) 
	{
		switch (m->getType()) {
		case MSG_REQUEST:
			processRequest((BazRequestMessage *)m);
			break;
		default:
			delete m;
			break;
		}
	}
	void processRequest(BazRequestMessage *m) 
	{
		// the only difference from Bar class
		counter_ -= m->arg_; 

		m->requestor_->post(Queue::PRIORITY_HIGH, 
			new BazReplyMessage(m->getRetType(), 
				m->getContext(), counter_));
		delete m;
	}

	int counter_;
};

// the way to find the baz thread, will be
// initialized from main()
static BazQueueThread *baz;

// [[ex06fc]]
// DEBUG descr The queue handler of the function foo() simulating locks to achieve atomicity across two calls.
class FooQueueThread : public QueueThread
{
public:
	enum FooMessageTypes {
		MSG_REQUEST,
		MSG_DELAYED_CALL_BAR,
		MSG_CALL_BAR,
		MSG_CALL_BAZ
	};
	// DEBUG {
	// the request that will be sent to foo
	class FooRequestMessage : public QueueMessage
	{
	public:
		QueueThread *requestor_; // where to send the reply

		FooRequestMessage(int rettype, 
				QueueThread *requestor, void *context) :
			QueueMessage(MSG_REQUEST, rettype, context),
			requestor_(requestor)
		{ }
	};
	// results returned from foo, just a notification
	// that it's done
	class FooReplyMessage : public QueueMessage
	{
	public:
		FooReplyMessage(int rettype, void *context) :
			QueueMessage(rettype, -1, context)
		{ }
	};
	// DEBUG }
	// ...
	// for synchronization around bar and baz
	class FooBarBazMessage : public QueueMessage
	{
	public:
		FooBarBazMessage(void *context) :
			QueueMessage(MSG_DELAYED_CALL_BAR, -1, context)
		{ }
	};

	FooQueueThread() :
		inBarBaz_(false)
	{ }

protected:
	struct FooContext {
		FooRequestMessage *request_;
		int a_;
		int b_;
		int c_;
	};
	void dispatch(QueueMessage *m) 
	{
		switch (m->getType()) {
		case MSG_REQUEST:
			processRequest((FooRequestMessage *)m);
			break;
		case MSG_DELAYED_CALL_BAR:
			processDelayedCallBar((FooBarBazMessage *)m);
			break;
		case MSG_CALL_BAR:
			processCallBar(
				(BarQueueThread::BarReplyMessage *)m);
			break;
		case MSG_CALL_BAZ:
			processCallBaz(
				(BazQueueThread::BazReplyMessage *)m);
			break;
		default:
			delete m;
			break;
		}
	}
	void processRequest(FooRequestMessage *m) 
	{
		FooContext *ctx = new FooContext;
		ctx->request_ = m;
	// DEBUG {
		ctx->b_ = 10;
	// DEBUG }
		// ... calculate ctx->b_
		if (!inBarBaz_) {
			inBarBaz_ = true;
			bar->post(Queue::PRIORITY_NORMAL, 
				new BarQueueThread::BarRequestMessage(
					MSG_CALL_BAR, ctx, this, ctx->b_));
		} else {
			// another request is calling bar and baz,
			// queue this one up
			delayBarBaz_.push_back(ctx);
	// DEBUG {
			fprintf(stderr, "foo delaying caller %p\n", ctx->request_->getContext());
	// DEBUG }
		}
	}
	void processDelayedCallBar(FooBarBazMessage *m) 
	{
		FooContext *ctx = (FooContext *)m->getContext();
		delete m;
		if (!inBarBaz_) {
	// DEBUG {
			fprintf(stderr, "foo continuing caller %p\n", ctx->request_->getContext());
	// DEBUG }
			inBarBaz_ = true;
			bar->post(Queue::PRIORITY_NORMAL, 
				new BarQueueThread::BarRequestMessage(
					MSG_CALL_BAR, ctx, this, ctx->b_));
		} else {
			// another request is calling bar and baz,
			// queue this one up
			delayBarBaz_.push_back(ctx);
	// DEBUG {
			fprintf(stderr, "foo repeated delaying caller %p\n", ctx->request_->getContext());
	// DEBUG }
		}
	}
	void processCallBar(
		BarQueueThread::BarReplyMessage *m) 
	{
		FooContext *ctx = (FooContext *)m->getContext();

		ctx->a_ = m->result_;
		delete m;
	// DEBUG {
		fprintf(stderr, "foo received result %d from bar\n", ctx->a_);
	// DEBUG }

		baz->post(Queue::PRIORITY_NORMAL, 
			new BazQueueThread::BazRequestMessage(
				MSG_CALL_BAZ, ctx, this, ctx->b_));
	}
	void processCallBaz(
		BazQueueThread::BazReplyMessage *m) 
	{
		FooContext *ctx = (FooContext *)m->getContext();

		ctx->c_ = m->result_;
		delete m;
	// DEBUG {
		fprintf(stderr, "foo received result %d from baz\n", ctx->c_);
	// DEBUG }
		inBarBaz_ = false;
		if (!delayBarBaz_.empty()) {
			FooContext *dctx = delayBarBaz_.front();
			delayBarBaz_.pop_front();
			this->post(Queue::PRIORITY_HIGH, 
				new FooBarBazMessage(dctx));
		}
		// ...
		FooRequestMessage *rq = ctx->request_;
		rq->requestor_->post(Queue::PRIORITY_HIGH, new 
			FooReplyMessage(rq->getRetType(), 
				rq->getContext()));
		delete rq;
		delete ctx;
	}

	// analog of the lock around bar and baz
	bool inBarBaz_;
	std::list<FooContext *> delayBarBaz_;
};
// END

// DEBUG {

// the way to find the foo thread, will be
// initialized from main()
static FooQueueThread *foo;

class DummyQueueThread : public QueueThread
{
public:
	virtual void dispatch(QueueMessage *m)
	{
		delete m;
	}
};

int main()
{
	Queue barQueue;
	bar = new BarQueueThread;
	bar->setQueue(&barQueue);

	Queue bazQueue;
	baz = new BazQueueThread;
	baz->setQueue(&bazQueue);

	Queue fooQueue;
	foo = new FooQueueThread;
	foo->setQueue(&fooQueue);

	bar->start();
	baz->start();
	foo->start();

	// ------------

	Queue mainQueue;
	DummyQueueThread mainThread;
	mainThread.setQueue(&mainQueue);

	foo->post(Queue::PRIORITY_NORMAL, new FooQueueThread::FooRequestMessage(0, &mainThread, (void *)1));
	foo->post(Queue::PRIORITY_NORMAL, new FooQueueThread::FooRequestMessage(0, &mainThread, (void *)2));

	FooQueueThread::FooReplyMessage *reply;
	for (int i = 0; i < 2; i++) {
		reply = (FooQueueThread::FooReplyMessage*)mainQueue.pop_front();
		fprintf(stderr, "foo returned reply %d\n", (int)(intptr_t)reply->getContext());
		delete reply;
	}
	
	// ------------
	
	bar->post(Queue::PRIORITY_NORMAL, new Message(Message::EXIT));
	baz->post(Queue::PRIORITY_NORMAL, new Message(Message::EXIT));
	foo->post(Queue::PRIORITY_NORMAL, new Message(Message::EXIT));

	bar->join();
	baz->join();
	foo->join();

	delete bar;
	delete baz;
	delete foo;
	return 0;
}
// DEBUG }
/* Sample output:

foo received result 10 from bar
foo delaying caller 0x2
foo received result -10 from baz
foo continuing caller 0x2
foo returned reply 1
foo received result 20 from bar
foo received result -20 from baz
foo returned reply 2

*/
